/* QtLua -- Lua bindings for Qt
   Copyright (C) 2011, Jarek Pelczar

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library; if not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301 USA

*/

#include "qluascriptdebugger_p_p.h"
#include "qluascriptsdebuggerscriptmodel_p.h"
#include "qluadebuggeragent_p.h"
#include "qluascriptprogram.h"
#include "qluascriptengine_p.h"
#include "qluascriptdebuggerwidgetfactoryinterface_p.h"
#include "qluascriptbreakpointswidgetinterface_p.h"
#include "qluascriptbreakpointmodel_p.h"
#include "qluascriptdebuggerstackmodel_p.h"
#include "qluascriptcontextinfo_p.h"
#include "qluascriptdebuggerstackwidgetinterface_p.h"
#include "qluascriptdebuggerlocalswidgetinterface_p.h"
#include "qluascriptdebuggerlocalsmodel_p.h"
#include "qluascriptdebuggercodewidgetinterface_p.h"
#include "qluascriptdebuggerscripteditorinterface_p.h"
#include <QEventLoop>

QLuaScriptDebuggerPrivate::QLuaScriptDebuggerPrivate()
{
    m_debugAgent = 0;
    m_scriptModel = 0;
    m_scriptWindow = 0;
    m_widgetFactory = NULL;
    m_breakpointModel = NULL;
    m_breakpointWindow = 0;
    m_stackModel = NULL;
    m_stackWindow = NULL;
    m_localsWindow = NULL;
    m_localsModel = NULL;
    m_currentLoop = NULL;
    m_editorWindow = NULL;
    m_interruptAction = NULL;
    m_continueAction = NULL;
    m_stepIntoAction = NULL;
    m_stepOverAction = NULL;
    m_stepOutAction = NULL;
    m_runToCursorAction = NULL;
    m_runToNewScriptAction = NULL;
    m_actionDecision = NoDebugAction;
}

QLuaScriptDebuggerPrivate::~QLuaScriptDebuggerPrivate()
{
    if(m_scriptWindow && !m_scriptWindow->parent())
        delete m_scriptWindow;
    if(m_breakpointWindow && !m_breakpointWindow->parent())
        delete m_breakpointWindow;
    if(m_stackWindow && !m_stackWindow->parent())
        delete m_stackWindow;
    if(m_localsWindow && !m_localsWindow->parent())
        delete m_localsWindow;
    if(m_editorWindow && !m_editorWindow->parent())
        delete m_editorWindow;
}

void QLuaScriptDebuggerPrivate::_p_contextPop()
{
    if(m_stackModel)
        m_stackModel->popFrame();
}

void QLuaScriptDebuggerPrivate::_p_contextPush()
{
    if(m_stackModel)
        m_stackModel->pushFrame(QLuaScriptContextInfo(m_debugAgent->engine()->currentContext()));    
}

void QLuaScriptDebuggerPrivate::_p_exceptionCatch(qint64, const QLuaScriptValue&)
{

}

void QLuaScriptDebuggerPrivate::_p_exceptionThrow(qint64, const QLuaScriptValue&, bool)
{

}

void QLuaScriptDebuggerPrivate::breakIntoDebugger(qint64 sid)
{
#ifdef QT_DEBUG
    qDebug() << "Break into debugger";
#endif

    if(m_currentLoop)
    {
        qWarning() << q << "Trying to break twice";
        return;
    }

    emit q->stopped();

    _p_currentScriptChanged(sid);
    m_localsModel->loadWithContext(QLuaScriptContextInfo(m_debugAgent->engine()->currentContext()));
    m_actionDecision = NoDebugAction;
    QEventLoop loop;
    m_currentLoop = &loop;
#ifdef QT_DEBUG
    qDebug() << this << "Exec loop";
#endif
    loop.exec();
#ifdef QT_DEBUG
    qDebug() << this << "Loop continue";
#endif
    m_currentLoop = NULL;
    m_localsModel->loadWithContext(QLuaScriptContextInfo(NULL));
    emit q->started();
}

void QLuaScriptDebuggerPrivate::_p_functionEntry(qint64 id)
{
#ifdef QT_DEBUG
    qDebug() << "Function entry" << id;
#endif

    _p_currentScriptChanged(id);

//    if(m_actionDecision == StepIntoAction || m_actionDecision == FirstChanceBreakAction)
    {
        breakIntoDebugger(id);
    }
}

void QLuaScriptDebuggerPrivate::_p_functionExit(qint64, const QLuaScriptValue&)
{
}

void QLuaScriptDebuggerPrivate::_p_positionChange(qint64 id, int line, int)
{
#ifdef QT_DEBUG
    qDebug() << "Position change" << id << "line =" << line << m_actionDecision;
#endif

    _p_currentScriptChanged(id);
    if(m_editorWindow) {
        QLuaScriptDebuggerScriptEditorInterface * view = m_editorWindow->currentView();
        if(view)
            view->setExecutionLineNumber(line, false);
    }

    if(m_actionDecision == StepOverAction || m_actionDecision == FirstChanceBreakAction)
    {
        breakIntoDebugger(id);
    }
}

void QLuaScriptDebuggerPrivate::_p_scriptLoad(qint64 id, QString program, QString fileName, int lineNumber)
{
    QLuaScriptProgram script(program, fileName);

    if(m_scriptModel)
        m_scriptModel->addScript(id, script);
}

void QLuaScriptDebuggerPrivate::_p_scriptUnload(qint64 id)
{
    if(m_scriptModel)
        m_scriptModel->removeScript(id);
}

QLuaScriptDebugger::QLuaScriptDebugger(QObject *parent) :
    QObject(parent)
{
    d = new QLuaScriptDebuggerPrivate();
    d->q = this;
}

QLuaScriptDebugger::~QLuaScriptDebugger()
{
    delete d;
}

void QLuaScriptDebugger::setWidgetFactory(QLuaScriptDebuggerWidgetFactoryInterface * factory)
{
    d->m_widgetFactory = factory;
}

QMenu * QLuaScriptDebugger::createStandardMenu(QWidget * parentWidget, QObject * actionParent)
{
    QMenu * menu = new QMenu(parentWidget);
    menu->setTitle(tr("Debug"));
    menu->addAction(action(QLuaScriptEngineDebugger::ContinueAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::InterruptAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::StepIntoAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::StepOverAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::StepOutAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::RunToCursorAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::RunToNewScriptAction, actionParent));
    menu->addSeparator();
    menu->addAction(action(QLuaScriptEngineDebugger::ToggleBreakpointAction, actionParent));
    menu->addSeparator();
    menu->addAction(action(QLuaScriptEngineDebugger::ClearDebugOutputAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::ClearErrorLogAction, actionParent));
    menu->addAction(action(QLuaScriptEngineDebugger::ClearConsoleAction, actionParent));
    return menu;
}

QToolBar * QLuaScriptDebugger::createStandardToolBar(QWidget * parentWidget, QObject * actionParent)
{
    QToolBar * tb = new QToolBar(parentWidget);
    tb->setObjectName(QLatin1String("qtluadebugger_standardToolBar"));
    tb->addAction(action(QLuaScriptEngineDebugger::ContinueAction, actionParent));
    tb->addAction(action(QLuaScriptEngineDebugger::InterruptAction, actionParent));
    tb->addAction(action(QLuaScriptEngineDebugger::StepIntoAction, actionParent));
    tb->addAction(action(QLuaScriptEngineDebugger::StepOverAction, actionParent));
    tb->addAction(action(QLuaScriptEngineDebugger::StepOutAction, actionParent));
    tb->addAction(action(QLuaScriptEngineDebugger::RunToCursorAction, actionParent));
    tb->addAction(action(QLuaScriptEngineDebugger::RunToNewScriptAction, actionParent));
    tb->addSeparator();
    tb->addAction(action(QLuaScriptEngineDebugger::FindInScriptAction, actionParent));
    return tb;
}

QAction * QLuaScriptDebugger::action(QLuaScriptEngineDebugger::DebuggerAction action, QObject * actionParent)
{
    switch(action)
    {
    case QLuaScriptEngineDebugger::InterruptAction:
        return d->interruptAction(actionParent);
    case QLuaScriptEngineDebugger::ContinueAction:
        return d->continuteAction(actionParent);
    case QLuaScriptEngineDebugger::StepIntoAction:
        return d->stepIntoAction(actionParent);
    case QLuaScriptEngineDebugger::StepOverAction:
        return d->stepOverAction(actionParent);
    case QLuaScriptEngineDebugger::StepOutAction:
        return d->stepOutAction(actionParent);
    case QLuaScriptEngineDebugger::RunToCursorAction:
        return d->runToCursorAction(actionParent);
    case QLuaScriptEngineDebugger::RunToNewScriptAction:
        return d->runToNewScriptAction(actionParent);
    default:
        return NULL;
    }
}

void QLuaScriptDebugger::attachAgent(QLuaDebuggerAgent * agent)
{
    if(!agent) {
        detachAgent();
        return;
    }

    if(!d->m_scriptModel) {
        d->m_scriptModel = new QLuaScriptsDebuggerScriptModel(this);
        if(d->m_scriptWindow)
            d->m_scriptWindow->attachModel(d->m_scriptModel);
        if(d->m_breakpointWindow)
            d->m_breakpointWindow->setScriptsModel(d->m_scriptModel);
        if(d->m_editorWindow)
            d->m_editorWindow->setScriptsModel(d->m_scriptModel);
    }

    if(!d->m_breakpointModel) {
        d->m_breakpointModel = new QLuaScriptBreakpointModel(this);
        if(d->m_breakpointWindow)
            d->m_breakpointWindow->setBreakpointsModel(d->m_breakpointModel);
        if(d->m_editorWindow)
            d->m_editorWindow->setBreakpointsModel(d->m_breakpointModel);
    }

    if(!d->m_stackModel) {
        d->m_stackModel = new QLuaScriptDebuggerStackModel(this);
        if(d->m_stackWindow)
            d->m_stackWindow->attachModel(d->m_stackModel);
    }

    if(!d->m_localsModel) {
        d->m_localsModel = new QLuaScriptDebuggerLocalsModel(this);
        if(d->m_localsWindow)
            d->m_localsWindow->attachModel(d->m_localsModel);
    }

    QList<QLuaScriptContextInfo> infos;
    QLuaScriptEngine * engine = agent->engine();
    QLuaScriptContext * context = engine->currentContext();
    while(context) {
        infos.append(QLuaScriptContextInfo(context));
        context = context->parentContext();
    }
    d->m_stackModel->setCallStack(infos);
    if(d->m_stackWindow)
        d->m_stackWindow->attachModel(d->m_stackModel);

    d->m_debugAgent = agent;            
    d->m_scriptModel->attachEngine(agent->engine());

    connect(agent, SIGNAL(_p_contextPush()), SLOT(_p_contextPush()));
    connect(agent, SIGNAL(_p_contextPop()), SLOT(_p_contextPop()));
    connect(agent, SIGNAL(_p_exceptionCatch(qint64,QLuaScriptValue)), SLOT(_p_exceptionCatch(qint64,QLuaScriptValue)));
    connect(agent, SIGNAL(_p_exceptionThrow(qint64,QLuaScriptValue,bool)), SLOT(_p_exceptionThrow(qint64,QLuaScriptValue,bool)));
    connect(agent, SIGNAL(_p_functionEntry(qint64)), SLOT(_p_functionEntry(qint64)));
    connect(agent, SIGNAL(_p_functionExit(qint64,QLuaScriptValue)), SLOT(_p_functionExit(qint64,QLuaScriptValue)));
    connect(agent, SIGNAL(_p_positionChange(qint64,int,int)), SLOT(_p_positionChange(qint64,int,int)));
    connect(agent, SIGNAL(_p_scriptLoad(qint64,QString,QString,int)), SLOT(_p_scriptLoad(qint64,QString,QString,int)));
    connect(agent, SIGNAL(_p_scriptUnload(qint64)), SLOT(_p_scriptUnload(qint64)));
}

void QLuaScriptDebugger::detachAgent()
{
    if(d->m_scriptModel)
        d->m_scriptModel->detachEngine();
    if(d->m_debugAgent)
        d->m_debugAgent->disconnect(this);
    d->m_debugAgent = NULL;
}

QWidget * QLuaScriptDebugger::debuggerWidget(QLuaScriptEngineDebugger::DebuggerWidget widget) const
{
    if(widget == QLuaScriptEngineDebugger::ScriptsWidget) {
        if(!d->m_scriptWindow) {
            if(!d->m_widgetFactory)
                return NULL;
            d->m_scriptWindow = qobject_cast<QLuaScriptDebuggerScriptWindowInterface *>(
                    d->m_widgetFactory->createWidget(widget));
            if(!d->m_scriptWindow)
                return NULL;
            if(d->m_scriptModel)
                d->m_scriptWindow->attachModel(d->m_scriptModel);
        }
        return d->m_scriptWindow;
    }

    if(widget == QLuaScriptEngineDebugger::BreakpointsWidget) {
        if(!d->m_breakpointWindow) {
            if(!d->m_widgetFactory)
                return NULL;
            d->m_breakpointWindow = qobject_cast<QLuaScriptBreakpointsWidgetInterface *>(
                        d->m_widgetFactory->createWidget(widget));
            if(!d->m_breakpointWindow)
                return NULL;
            if(d->m_scriptModel)
                d->m_breakpointWindow->setScriptsModel(d->m_scriptModel);
            if(d->m_breakpointModel)
                d->m_breakpointWindow->setBreakpointsModel(d->m_breakpointModel);
        }
        return d->m_breakpointWindow;
    }

    if(widget == QLuaScriptEngineDebugger::StackWidget) {
        if(!d->m_stackWindow) {
            if(!d->m_widgetFactory)
                return NULL;
            d->m_stackWindow = qobject_cast<QLuaScriptDebuggerStackWidgetInterface *>(
                        d->m_widgetFactory->createWidget(widget));
            if(!d->m_stackWindow)
                return NULL;
            if(d->m_stackModel)
                d->m_stackWindow->attachModel(d->m_stackModel);
        }
        return d->m_stackWindow;
    }

    if(widget == QLuaScriptEngineDebugger::LocalsWidget) {
        if(!d->m_localsWindow) {
            if(!d->m_widgetFactory)
                return NULL;
            d->m_localsWindow = qobject_cast<QLuaScriptDebuggerLocalsWidgetInterface *>(
                        d->m_widgetFactory->createWidget(widget));
            if(!d->m_localsWindow)
                return NULL;
            if(d->m_stackModel)
                d->m_localsWindow->attachModel(d->m_localsModel);
        }
        return d->m_localsWindow;
    }

    if(widget == QLuaScriptEngineDebugger::CodeWidget) {
        if(!d->m_editorWindow) {
            if(!d->m_widgetFactory)
                return NULL;
            d->m_editorWindow = qobject_cast<QLuaScriptDebuggerCodeWidgetInterface *>(
                        d->m_widgetFactory->createWidget(widget));
            if(!d->m_editorWindow)
                return NULL;
            if(d->m_scriptModel)
                d->m_editorWindow->setScriptsModel(d->m_scriptModel);
            if(d->m_breakpointModel)
                d->m_editorWindow->setBreakpointsModel(d->m_breakpointModel);
        }
        return d->m_editorWindow;
    }

    return NULL;
}

void QLuaScriptDebuggerPrivate::_p_currentScriptChanged(qint64 id)
{
    if(m_editorWindow && m_editorWindow->currentScriptId() != id) {
        m_editorWindow->setCurrentScript(id);
    }
}

QAction * QLuaScriptDebuggerPrivate::continuteAction(QObject * actionParent)
{
    if(!m_continueAction) {
        m_continueAction = new QAction(QLuaScriptDebugger::tr("Continue"), actionParent);
        m_continueAction->setShortcut(QLuaScriptDebugger::tr("F5"));
        QObject::connect(m_continueAction, SIGNAL(triggered()),
                         q, SLOT(_p_continue()));
    }
    return m_continueAction;
}

QAction * QLuaScriptDebuggerPrivate::interruptAction(QObject * actionParent)
{
    if(!m_interruptAction) {
        m_interruptAction = new QAction(QLuaScriptDebugger::tr("Interrupt"), actionParent);
        m_interruptAction->setShortcut(QLuaScriptDebugger::tr("Shift+F5"));
        QObject::connect(m_interruptAction, SIGNAL(triggered()),
                         q, SLOT(_p_interrupt()));
    }
    return m_interruptAction;
}

QAction * QLuaScriptDebuggerPrivate::stepIntoAction(QObject * actionParent)
{
    if(!m_stepIntoAction) {
        m_stepIntoAction = new QAction(QLuaScriptDebugger::tr("Step Into"), actionParent);
        m_stepIntoAction->setShortcut(QLuaScriptDebugger::tr("F11"));
        QObject::connect(m_stepIntoAction, SIGNAL(triggered()),
                         q, SLOT(_p_stepInto()));
    }
    return m_stepIntoAction;
}

QAction * QLuaScriptDebuggerPrivate::stepOverAction(QObject * actionParent)
{
    if(!m_stepOverAction) {
        m_stepOverAction = new QAction(QLuaScriptDebugger::tr("Step Over"), actionParent);
        m_stepOverAction->setShortcut(QLuaScriptDebugger::tr("F10"));
        QObject::connect(m_stepOverAction, SIGNAL(triggered()),
                         q, SLOT(_p_stepOver()));
    }
    return m_stepOverAction;
}

QAction * QLuaScriptDebuggerPrivate::stepOutAction(QObject * actionParent)
{
    if(!m_stepOutAction) {
        m_stepOutAction = new QAction(QLuaScriptDebugger::tr("Step Out"), actionParent);
        m_stepOutAction->setShortcut(QLuaScriptDebugger::tr("Shift+F11"));
        QObject::connect(m_stepOutAction, SIGNAL(triggered()),
                         q, SLOT(_p_stepOut()));
    }
    return m_stepOutAction;
}

QAction * QLuaScriptDebuggerPrivate::runToCursorAction(QObject * actionParent)
{
    if(!m_runToCursorAction) {
        m_runToCursorAction = new QAction(QLuaScriptDebugger::tr("Run to Cursor"), actionParent);
        m_runToCursorAction->setShortcut(QLuaScriptDebugger::tr("Ctrl+F10"));
        QObject::connect(m_runToCursorAction, SIGNAL(triggered()),
                         q, SLOT(_p_runToCursor()));
    }
    return m_runToCursorAction;
}

QAction * QLuaScriptDebuggerPrivate::runToNewScriptAction(QObject * actionParent)
{
    if(!m_runToNewScriptAction) {
        m_runToNewScriptAction = new QAction(QLuaScriptDebugger::tr("Run to New Script"), actionParent);
        QObject::connect(m_runToNewScriptAction, SIGNAL(triggered()),
                         q, SLOT(_p_runToNewScript()));
    }
    return m_runToNewScriptAction;
}

void QLuaScriptDebuggerPrivate::_p_continue()
{
    if(m_currentLoop) {
#ifdef QT_DEBUG
        qDebug() << q << "Continue";
#endif
        m_currentLoop->exit();
        m_actionDecision = NoDebugAction;
    }
}

void QLuaScriptDebuggerPrivate::_p_interrupt()
{
    if(!m_currentLoop) {
#ifdef QT_DEBUG
        qDebug() << q << "Interrupt";
#endif
        m_actionDecision = FirstChanceBreakAction;
    }
}

void QLuaScriptDebuggerPrivate::_p_stepInto()
{
    if(m_currentLoop) {
#ifdef QT_DEBUG
        qDebug() << q << "Step into";
#endif
        m_currentLoop->exit();
        m_actionDecision = StepIntoAction;
    }
}

void QLuaScriptDebuggerPrivate::_p_stepOver()
{
    if(m_currentLoop) {
#ifdef QT_DEBUG
        qDebug() << q << "Step over";
#endif
        m_currentLoop->exit();
        m_actionDecision = StepOverAction;
    }
}

void QLuaScriptDebuggerPrivate::_p_runToCursor()
{

}

void QLuaScriptDebuggerPrivate::_p_runToNewScript()
{

}

void QLuaScriptDebuggerPrivate::_p_stepOut()
{
    if(m_currentLoop) {
#ifdef QT_DEBUG
        qDebug() << q << "Step out";
#endif
        m_currentLoop->exit();
        m_actionDecision = StepOutAction;
    }
}

#include "moc_qluascriptdebugger_p.cpp"
